/**
  ETFDAQ Project
  \class TAMesytec
  \brief A VME card produced by Mesytec Corp. from Germany
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-09-05
  \date 2024-09-05 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include <unistd.h>
#include "TAMesytec.h"
#include "TAMsg.h"
#include "TAYaml.h"
#include "mesy_reg.h"

using std::cout;
using std::endl;

#define er TAMsg::Error

TAMesytec::TAMesytec(TAController *ctrl, const TAYaml &c) : TACard(ctrl, c),
fRes(2), fDataLen(0), fBankOp(0), fDataLenFormat(2){
  fMAX_EVCTR = 0xffffffff - 100;
} // end ctor

TAMesytec::~TAMesytec(){}

// config the card
void TAMesytec::Initialize(){
  Config(); // read user config info from yaml file to member variables
  SoftwareReset();
  usleep(1);
  SlotInit();
  Identify();
  RegWriteA32D16(mesyreg::start_acq, 0x0); // reject any triggers, as I'm initing

  const auto &c = fConfig; // just for convenience
  // set resolution
  fRes = 2;
  if(c["resolution"]) fRes = c["resolution"].as<uint16_t>();
  SetResolution(fRes);
  if(c["bankop"]) fBankOp = c["bankop"].as<uint16_t>();
  if(fBankOp != 0 && fBankOp != 1 && fBankOp != 3)
    er("TAMesytec", "Initialize: illegal bank opration: %d "
      "(allowed: 0->connteced, 1->independent, 3->zero deadtime)", fBankOp);
  RegWriteA32D16(mesyreg::bank_operation, fBankOp);
  // set threshods
  for(size_t i = fThres.size(); i--;) SetThreshold(i, fThres[i]);
  RegWriteA32D16(mesyreg::start_acq, 0x1); // start accetping triggers
} // end member function Initialize

// read card info
void TAMesytec::Identify(){
  fFirmWareRev = RegReadA32D16(mesyreg::firmware_revision);
} // end member function Identify

// write slot id and crate id
void TAMesytec::SlotInit(){
  RegWriteA32D16(mesyreg::module_id, fSlot);
} // end member function SlotInit

// level 1~7, 0 will disable the IRQ generation
// vector 0x00~0xFF, requested by vme ctrler to identify the interrupter
// n: trigger will be generated after nof buffered events (or data words,
// depending on isEv) reaches n
// isEv: whether use event or data trigger
void TAMesytec::ConfigIRQ(int level, int vector, int n, bool isEv) const{
  if(level){
    RegWriteA32D16(mesyreg::irq_level, level & 0x7);
    RegWriteA32D16(mesyreg::irq_vector, vector & 0xff);
    if(isEv){
      RegWriteA32D16(mesyreg::irq_source, 0x0); // using event threshold
      RegWriteA32D16(mesyreg::irq_event_thre, n & 0x7fff);
    } // end if
    else{
      RegWriteA32D16(mesyreg::irq_source, 0x1); // using data word threshold
      RegWriteA32D16(mesyreg::irq_data_thre, n & 0x7fff);
    } // end else
  } // end if
} // end member function ConfigIRQ

void TAMesytec::SoftwareReset(){
  RegWriteA32D16(mesyreg::soft_reset, 0x0);
} // end member function SoftwareReset

// true if buffered events are ready
bool TAMesytec::DataReady() const{
  return RegReadA32D16(mesyreg::data_ready) & 0x1;
} // end member function DataReady

void TAMesytec::ReadEventCounter(){
  fDataLen = RegReadA32D16(mesyreg::buffer_data_length);
  if(fDebug){
    TellYouWhoIAm();
    cout << "\n\e[1m____This is event counter____ \e[0m"<< endl;
    cout << "dataLen: " << fDataLen << ", data_len_format: " << fDataLenFormat << endl;
  } // end if
} // end member function ReadEventCounter

int TAMesytec::ReadData(uint32_t *buf){
  ReadEventCounter(); // read the event counter
  int len = 0;
  for(int i = fDataLen; i--;) len += ReadEvent(buf + len);
  return len;
} // end member function ReadData

// change the header to be compatible with CAEN cards
// 1. move module id [16:20] to [27:31]
// 2. set [26] to 0, and move hsig [30:31] to [24:25]
// 3. move [0:15] (nw+res) to [8:23]
// 4. set [0:7] to 0 permanently
void TAMesytec::ModifyHeader(uint32_t &h){
  // h = ((h & 0x1F0000) << 11) // 1. move module id [16:20] to [27:31]
  //   | ((h & 0xc0000000) >> 6) // 2. move hsig [30:31] to [24:25]
  //   | ((h & 0xFFFF) << 8); // 3. move [0:15] (nw+res) to [8:23]
  // *. [0:7] and [26] sett to 0 are automatic
  h = ((h & 0x1F0000) << 11) | ((h & 0xc0000000) >> 6) | ((h & 0xFFFF) << 8);
} // end member function ModifyHeader

// \retval nof words read
int TAMesytec::ReadEvent(uint32_t *buf){
  buf[0] = RegReadA32D32(mesyreg::dataFIFO);
  const int nw = buf[0] & 0xFFF; // nof following words for the event
  if(fDebug) cout << "nw: " << nw << endl; // DEBUG

  ModifyHeader(buf[0]); // change the header to be compatible with CAEN cards

  int nget = 0;

// #define MBLT64_READ
#ifndef MBLT64_READ
  nget = RegReadA32BLT32(mesyreg::dataFIFO, buf+1, nw) + 1;
#else
  if(nw % 2){ // odd words to read
    buf[1] = RegReadA32D32(mesyreg::dataFIFO);
    nget = RegReadA32MBLT64(mesyreg::dataFIFO, buf+2, nw-1) + 2;
  } // end if
  else nget = RegReadA32MBLT64(mesyreg::dataFIFO, buf+1, nw) + 1;
#endif

  // data length should be pair of 32-bit words - so the next should not happen
  // if(nget%2){
  //   TAMsg::Warn("TAMesytec", "ReadEvent: odd event length: %d", nw);
  //   nw++; // blt64 alignment
  // } // end if
  if(fDebug) PrintEvent(buf);
  return nget;
} // end member function ReadEvent

// clear the module's multi-event buffers, the R/W pointers, and the event counters
void TAMesytec::ClearData(){
  RegWriteA32D16(mesyreg::FIFO_reset, 0x0); // init fifo
  // clear berr and allows next readout
  RegWriteA32D16(mesyreg::readout_reset, 0x0);
  if(fEventCounter > fMAX_EVCTR) ClearEventCounter();
} // end member function  // data reset (DR)

void TAMesytec::ResetForNewRun(){
  ClearEventCounter();
} // end member function ResetForNewRun

// clears the event counter
void TAMesytec::ClearEventCounter(){
  fEventCounter = fDNEv = 0;
  RegWriteA32D16(mesyreg::reset_ctr_ab, 0b01);
} // end member function ClearEventCounter

void TAMesytec::Print() const{
  printf("\n\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  printf("baseaddr: 0x%08x, firmware version: %d\n", fBaseAddress, fFirmWareRev);
  cout << std::boolalpha;
  cout << "Keep Underflow: " << fIsKeepUnderflow;
  cout << ",\t Keep Overflow: " << fIsKeepOverflow << endl;
  static const char *bop[3] = {"connected", "independent", "zero dead time"};
  cout << "Bank Operation: \e[32;1m" << bop[fBankOp] << "\e[0m" << endl;
  cout << std::noboolalpha;
  if(fIRQLevel){
    cout << "\033[1mIRQ Level: " << fIRQLevel << ",\t IRQ Vector: ";
    cout << std::hex << fIRQVector << std::dec;
    cout << ",\t Event Trigger: " << fEventTrigger << "\033[0m" << endl;
  } // end if
  if(fThres.size()){
    // print thresholds and the killbit
    cout << "___ Thresholds for all the " << fNCh << " channels ___";
    for(size_t i = 0; i < fNCh; i++){
      if(!(i%4)) cout << endl;
      const int th = RegReadA32D16(mesyreg::thresholds + i*fThreAddrIncre) & 0x1FFF;
      printf("ch%02zu: %u\t", i, th);
    } // end for
    cout << endl;
  } // end if
} // end member function Print

void TAMesytec::PrintEvent(const uint32_t *ev){
  printf("\n\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  cout << "\033[32;1m-------- Event Print -------- \033[0m" << endl;

  // the header has been MODIFIED to be compatible with CAEN decoding codes
  // and distinguishable from V1x90 cards
  // see TAMesytec::ModifyHeader(uint32_t &h) for details
  const uint32_t header = ev[0];
  const int nw = (header>>8 & 0xFFF); // nof following words
  const int res = header>>20 & 0xF;
  int sig = header>>24 & 0x3;
  const int slot = header>>27 & 0x1F;

  static const char *pos[] = {"data", "header", "UDF", "trailer", "err",
    "err", "err", "err", "err", "err", "err"};
  printf("%s: slot: %d, ev_len: %d, res: %d\n", pos[sig], slot, nw + 1, res);
  for(int i = 1; i < nw; i++){
    const uint32_t data = ev[i];
    const int sig = data>>30 & 0x3;
    const bool trig = data>>21 & 0x1;
    const int chid = data>>16 & 0x1F;
    const int tdc = data & 0xFFFF;
    printf("%s, trig: %d, chid: %02d, tdc: %d\n", pos[sig], trig, chid, tdc);
  } // end for over i
  const uint32_t trailer = ev[nw];
  printf("%s, event counter: %d\n", pos[trailer>>30 & 0x3], trailer & 0x1FFFFFFF);
  fflush(stdout);
  cout << "\033[32;1m-------- BYE -------- \033[0m" << endl;
} // end member function PrintEvent
